package sbc.group1.lab1.alt;

import java.awt.Color;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

import sbc.group1.lab1.common.Car;
import sbc.group1.lab1.common.CarBody;
import sbc.group1.lab1.common.Coatable;
import sbc.group1.lab1.common.Engine;
import sbc.group1.lab1.common.Order;
import sbc.group1.lab1.common.Part;
import sbc.group1.lab1.common.Wheel;

public class CarServiceImplementation implements CarService {
	private static class CarComparator implements Comparator<Car> {

		@Override
        public int compare(Car car1, Car car2) {
            if (car1.getOrder()!=null) {
                if (car2.getOrder()!=null) {
                    if (car1.getOrder().getId()==car2.getOrder().getId()) {
                        return car1.getEngine().getId()-car2.getEngine().getId();
                    }else {
                        return car1.getOrder().getId()-car2.getOrder().getId();
                    }
                }else {
                    return -1;
                }
            }else {
                if (car2.getOrder()!=null) {
                    return 1;
                }else {
                	return car1.getEngine().getId()-car2.getEngine().getId();
                }
            }
        }
	}
	private class AsyncNotifyJob implements Runnable {
		private Object o;
		private RemoteListener listener;

		public AsyncNotifyJob(RemoteListener listener, Object o) {
			this.o = o;
			this.listener = listener;
		}

		@Override
		public void run() {
			try {
				listener.onEvent(o);
			} catch (RemoteException e) {
				CarServiceImplementation.this.unregisterListener(listener);
			}
		}
	}

	private ConcurrentSkipListSet<Car> lstCar;
	private ArrayList<Engine> lstEngine;
	private ArrayList<CarBody> lstCarBody;
	private ArrayList<Wheel> lstWheel;
	private ArrayList<Car> lstCarFinished;
	private PriorityBlockingQueue<Car> lstCompleteCarTested;
	private PriorityBlockingQueue<Car> lstPartialCarTestedOnDefects;
	private PriorityBlockingQueue<Car> lstPartialCarTestedOnCompleteness;
	private ArrayList<Car> lstDefectCar;
	private ArrayList<Order> lstOrder;
	private HashMap<Integer, Integer> mapDoneCarByOrder;

	private final Object lstCarLock = new Object();
	private final Object lstCarBodyLock = lstCarLock;
	private final Object lstCoatableLock = lstCarLock;
	private final Object testPartialTestedOnDefectsLock = new Object();
	private final Object testPartialTestedOnCompletenessLock = new Object();
	private final AtomicInteger currentId = new AtomicInteger(0);
	private ExecutorService executorService = Executors
			.newSingleThreadExecutor();
	private Map<Containers, List<RemoteListener>> allListenersAdd = new ConcurrentHashMap<Containers, List<RemoteListener>>();
	private Map<Containers, List<RemoteListener>> allListenersRemove = new ConcurrentHashMap<Containers, List<RemoteListener>>();
	private Map<Order, LinkedBlockingQueue<AssemblyJob>> assemblerJobs;
	private Map<Order, LinkedBlockingQueue<CoaterJob>> coaterJobs;
	private int orderId = 0;
	private ConcurrentHashMap<Order, LinkedBlockingQueue<CarBody>> orderedCarBodies = 
			new ConcurrentHashMap<Order, LinkedBlockingQueue<CarBody>>();
	public CarServiceImplementation() { 
		lstCar = new ConcurrentSkipListSet<Car>(new CarComparator());
		lstDefectCar = new ArrayList<Car>();
		lstEngine = new ArrayList<Engine>();
		lstCarBody = new ArrayList<CarBody>();
		lstWheel = new ArrayList<Wheel>();
		lstCarFinished = new ArrayList<Car>();
		lstCompleteCarTested = new PriorityBlockingQueue<Car>(1,new CarComparator());
		lstPartialCarTestedOnDefects = new PriorityBlockingQueue<Car>(1,new CarComparator());
		lstPartialCarTestedOnCompleteness = new PriorityBlockingQueue<Car>(1,new CarComparator());
		lstOrder = new ArrayList<Order>();
		this.mapDoneCarByOrder = new HashMap<Integer, Integer>();
		assemblerJobs = new ConcurrentHashMap<Order, LinkedBlockingQueue<AssemblyJob>>();
		coaterJobs = new ConcurrentHashMap<Order, LinkedBlockingQueue<CoaterJob>>();
		
		allListenersRemove.put(getEnum(lstCarBody), new CopyOnWriteArrayList<RemoteListener>());
		allListenersAdd.put(getEnum(lstCarBody), new CopyOnWriteArrayList<RemoteListener>());
		
		allListenersRemove.put(getEnum(lstEngine), new CopyOnWriteArrayList<RemoteListener>());
		allListenersAdd.put(getEnum(lstEngine), new CopyOnWriteArrayList<RemoteListener>());
		
		allListenersRemove.put(getEnum(lstWheel), new CopyOnWriteArrayList<RemoteListener>());
		allListenersAdd.put(getEnum(lstWheel), new CopyOnWriteArrayList<RemoteListener>());
		
		allListenersRemove.put(getEnum(lstCar), new CopyOnWriteArrayList<RemoteListener>());
		allListenersAdd.put(getEnum(lstCar), new CopyOnWriteArrayList<RemoteListener>());
		
		allListenersRemove.put(getEnum(lstPartialCarTestedOnCompleteness), new CopyOnWriteArrayList<RemoteListener>());
		allListenersAdd.put(getEnum(lstPartialCarTestedOnCompleteness), new CopyOnWriteArrayList<RemoteListener>());
		
		allListenersRemove.put(getEnum(lstPartialCarTestedOnDefects), new CopyOnWriteArrayList<RemoteListener>());
		allListenersAdd.put(getEnum(lstPartialCarTestedOnDefects), new CopyOnWriteArrayList<RemoteListener>());
		
		allListenersRemove.put(getEnum(lstCompleteCarTested), new CopyOnWriteArrayList<RemoteListener>());
		allListenersAdd.put(getEnum(lstCompleteCarTested), new CopyOnWriteArrayList<RemoteListener>());
		
		allListenersRemove.put(getEnum(lstCarFinished), new CopyOnWriteArrayList<RemoteListener>());
		allListenersAdd.put(getEnum(lstCarFinished), new CopyOnWriteArrayList<RemoteListener>());
		
		allListenersRemove.put(getEnum(lstDefectCar), new CopyOnWriteArrayList<RemoteListener>());
		allListenersAdd.put(getEnum(lstDefectCar), new CopyOnWriteArrayList<RemoteListener>());
		allListenersRemove.put(getEnum(lstOrder), new CopyOnWriteArrayList<RemoteListener>());
		allListenersAdd.put(getEnum(lstOrder), new CopyOnWriteArrayList<RemoteListener>());
	}

	public void addCarBody(CarBody newCarBody) throws RemoteException {
		makeId(newCarBody);
		synchronized (lstCarBodyLock) {
			lstCarBody.add(newCarBody);
			onAdd(newCarBody, lstCarBody);
			lstCarBodyLock.notifyAll();
		}
	}

	private void makeId(Part part) {
		if (!part.hasId()) {
			part.setId(currentId.incrementAndGet());
		}
	}

	@Override
	public void addWheel(Wheel newWheel) throws RemoteException {
		makeId(newWheel);
		synchronized (lstWheel) {
			lstWheel.add(newWheel);
			onAdd(newWheel, lstWheel);
			lstWheel.notifyAll();
		}
	}

	@Override
	public void addEngine(Engine newEngine) throws RemoteException {
		makeId(newEngine);
		synchronized (lstEngine) {
			lstEngine.add(newEngine);
			onAdd(newEngine, lstEngine);
			lstEngine.notifyAll();
		}
	}

	public Wheel TakeWheel() throws RemoteException, InterruptedException {
		synchronized (lstWheel) {
			while (true) {
				int lastIndex = lstWheel.size() - 1;
				Wheel w = null;
				if (lastIndex >= 0) {
					w = lstWheel.get(lastIndex);
					lstWheel.remove(lastIndex);
					onRemove(w, lstWheel);
					return w;
				} else {
					lstWheel.wait();
				}

			}
		}
	}

	@Override
	public void addCar(Car newCar) throws RemoteException, InterruptedException {

		if (newCar.isMarkedAsFinished()) {
			synchronized (lstCarFinished) {
				lstCarFinished.add(newCar);
				
				if(newCar.getOrder()!= null){
		        	int count = 0;
		        	if(mapDoneCarByOrder.containsKey(newCar.getOrder().getId())){
		        		count = mapDoneCarByOrder.get(newCar.getOrder().getId());
		        	}
	        		count ++;
	        		mapDoneCarByOrder.put(newCar.getOrder().getId(), count);
		        	if(count == newCar.getOrder().getNumCars()){
		        		removeOrder(newCar.getOrder());
		        	}

		        }

				onAdd(newCar, lstCarFinished);
				lstCarFinished.notifyAll();
			}
		} else {
			synchronized (lstCarLock) {
				lstCar.add(newCar);
				onAdd(newCar, lstCar);
				lstCarLock.notifyAll();
				synchronized(testPartialTestedOnDefectsLock){
					testPartialTestedOnDefectsLock.notifyAll();
				}
				synchronized(testPartialTestedOnCompletenessLock){
					testPartialTestedOnCompletenessLock.notifyAll();
				}
			}
		}
	}
	
	@Override
	public void addDefectCar(Car defectCar) throws RemoteException {
		synchronized (lstCarLock) {
			lstDefectCar.add(defectCar);
		}

	}

	@Override
	public void addCoatable(Coatable coatable) throws RemoteException,
			InterruptedException {
		if (coatable instanceof Car) {
			addCar((Car) coatable);
		} else if (coatable instanceof CarBody) {
			addCarBody((CarBody) coatable);
		}
	}

	@Override
	public Coatable takeUncoatedCoatableCarsFirst() throws RemoteException,
			InterruptedException {
		synchronized (lstCoatableLock) {
			while (true) {
				Iterator<Car> iter = lstCar.iterator();
				while (iter.hasNext()) {
					Car car = iter.next();
					if (!car.isCoated()) {
						iter.remove();
						onRemove(car, lstCar);
						return car;
					}
				}
				Iterator<CarBody> iter2 = lstCarBody.iterator();
				while (iter2.hasNext()) {
					CarBody body = iter2.next();
					if (!body.isCoated()) {
						iter2.remove();
						onRemove(body, lstCarBody);
						return body;
					}
				}
				lstCoatableLock.wait();
			}
		}
	}
	
	@Override
	public Coatable takeUncoatedCoatableCarsFirst(Color c, int orderId) throws  RemoteException, InterruptedException  {
		synchronized (lstCoatableLock) {
				Iterator<Car> iter = lstCar.iterator();
				while (iter.hasNext()) {
					Car car = iter.next();
					if (!car.isCoated()) {
						if(car.getOrder() != null){
							if(car.getOrder().getId() == orderId && car.getOrder().getColor().equals(c))
							{
								iter.remove();
								onRemove(car, lstCar);
								return car;
							}
						}
					}
				}
				Iterator<CarBody> iter2 = lstCarBody.iterator();
				while (iter2.hasNext()) {
					CarBody body = iter2.next();
					if (!body.isCoated()) {
						iter2.remove();
						onRemove(body, lstCarBody);
						return body;
					}
				}
		}
		return null;
	}

	public Car TakeCar() throws RemoteException, InterruptedException {
		synchronized (lstCarLock) {
			while (true) {
				if (lstCar.size() > 0) {
					Car c = lstCar.pollFirst();
					onRemove(c, lstCar);
					return c;
				} else {
					lstCarLock.wait();
				}
			}
		}
	}

	public CarBody TakeCarBody() throws RemoteException, InterruptedException {
		CarBody cb = null;
		synchronized (lstCarBodyLock) {
			while (true) {
				int lastIndex = lstCarBody.size() - 1;
				if (lastIndex >= 0) {
					cb = lstCarBody.get(lastIndex);
					lstCarBody.remove(lastIndex);
					onRemove(cb, lstCarBody);
					return cb;
				} else {
					lstCarBodyLock.wait();
				}
			}
		}

	}
	
	public CarBody TakeCarBody(Order o) throws RemoteException, InterruptedException {
		return orderedCarBodies.get(o).take();
	}
	
	public void addCarBody(Order o, CarBody body) throws RemoteException, InterruptedException {
		orderedCarBodies.get(o).add(body);
	}

	public Engine TakeEngine() throws RemoteException, InterruptedException {
		synchronized (lstEngine) {
			while (true) {
				int lastIndex = lstEngine.size() - 1;
				Engine e = null;
				if (lastIndex >= 0) {
					e = lstEngine.get(lastIndex);
					lstEngine.remove(lastIndex);
					onRemove(e, lstEngine);
					return e;
				} else {
					lstEngine.wait();
				}

			}
		}
	}
	
	public Engine TakeEngine(Engine.Type type) throws RemoteException, InterruptedException {
		synchronized (lstEngine) {
			for(int i = 0; i < lstEngine.size(); i++){
				Engine e = lstEngine.get(i);
				if(e.getType().equals(type)){
					lstEngine.remove(i);
					onRemove(e, lstEngine);
					return e;
				}
			}
		}
		return null;
	}

	public Wheel ReadWheel() throws RemoteException, InterruptedException {
		synchronized (lstWheel) {
			while (true) {
				int lastIndex = lstWheel.size() - 1;
				Wheel w = null;
				if (lastIndex >= 0) {
					w = lstWheel.get(lastIndex);
					return w;
				} else {
					lstWheel.wait();
				}

			}
		}
	}

	public Car ReadCar() throws RemoteException, InterruptedException {
		synchronized (lstCar) {
			while (true) {
				int lastIndex = lstCar.size() - 1;
				Car c = null;
				if (lastIndex >= 0) {
					c = (Car) lstCar.toArray()[lastIndex];
					return c;
				} else {
					lstCarLock.wait();
				}
			}
		}
	}

	public CarBody ReadCarBody() throws RemoteException, InterruptedException {
		CarBody cb = null;
		synchronized (lstCarBodyLock) {
			while (true) {
				int lastIndex = lstCarBody.size() - 1;
				if (lastIndex >= 0) {
					cb = lstCarBody.get(lastIndex);
					return cb;
				} else {
					lstCarBodyLock.wait();
				}
			}
		}

	}

	public Engine ReadEngine() throws RemoteException, InterruptedException {
		synchronized (lstEngine) {
			while (true) {
				int lastIndex = lstEngine.size() - 1;
				Engine e = null;
				if (lastIndex >= 0) {
					e = lstEngine.get(lastIndex);
					return e;
				} else {
					lstEngine.wait();
				}

			}
		}
	}

	private void onRemove(final Object o, Collection<?> lst) {
		onEvent(allListenersRemove, o, lst);
	}

	private void onAdd(Object o, Collection<?> lst) {
		onEvent(allListenersAdd, o, lst);
	}

	private void onEvent(Map<Containers, List<RemoteListener>> remoteListeners, Object o, Collection<?> lst) {
		synchronized (remoteListeners) {
				for (RemoteListener listener : remoteListeners.get(getEnum(lst))) {
					executorService.execute(new AsyncNotifyJob(listener, o));
				}
		}
	}

	public void registerAddListener(RemoteListener listener, Containers container)
			throws RemoteException {
		allListenersAdd.get(container).add(listener);
	}
	private Collection<?> getList(Containers container) {
		switch(container) {
		case BODIES:
			return lstCarBody;
		case WHEELS:
			return lstWheel;
		case ENGINES:
			return lstEngine;
		case ONCOMPLETENESSTESTEDCARS:
			return lstPartialCarTestedOnCompleteness;
		case ONDEFECTTESTEDCARS:
			return lstPartialCarTestedOnDefects;
		case TOTALTESTEDCARS:
			return lstCompleteCarTested;
		case TOTALFINISHEDCARS:
			return lstCarFinished;
		case DEFECTCARS:
			return lstDefectCar;
		case CARS:
			return lstCar;
		case ORDERS:
			return lstOrder;
			default:
				throw new AssertionError();
		}
		
	}
	private Containers getEnum(Collection<?> lst) {
		if (lst==lstCarBody) {
			return Containers.BODIES;
		}else if (lst==lstWheel){
			return Containers.WHEELS;
		}else if (lst==lstEngine){
			return Containers.ENGINES;
		}else if (lst==lstCompleteCarTested){
			return Containers.TOTALTESTEDCARS;
		}else if (lst==lstPartialCarTestedOnCompleteness){
			return Containers.ONCOMPLETENESSTESTEDCARS;
		}else if (lst==lstCar){
			return Containers.CARS;
		}else if (lst==lstPartialCarTestedOnDefects){
			return Containers.ONDEFECTTESTEDCARS;
		}else if (lst==lstCarFinished){
			return Containers.TOTALFINISHEDCARS;
		}else if (lst==lstDefectCar){
			return Containers.DEFECTCARS;
		}else if (lst==lstOrder){
			return Containers.ORDERS;
		}else {
			throw new AssertionError();
		}
	}
	
	public void registerRemoveListener(RemoteListener listener, Containers container)
			throws RemoteException {
			allListenersRemove.get(container).add(listener);
	}

	public void unregisterListener(RemoteListener listener) {
		// TODO 
	}

	@Override
	public void addCompleteTestedCar(Car testedCar) throws RemoteException,
			InterruptedException {
			lstCompleteCarTested.add(testedCar);
			onAdd(testedCar,lstCompleteCarTested);
			synchronized (lstCompleteCarTested) {
				lstCompleteCarTested.notifyAll();
			}
	}

	@Override
	public Car takeCompleteTestedCar() throws RemoteException,
			InterruptedException {
				Car c = lstCompleteCarTested.take();
				onRemove(c, lstCompleteCarTested);
				return c;
	}

	@Override
	public Car takeCarForCompletenessTester() throws RemoteException,
			InterruptedException {
		Car car = null;
		while (true) {
				car = lstPartialCarTestedOnDefects.poll();
				if (car != null){
					return car;
				}
			synchronized (lstCarLock) {
				Iterator<Car> iter2 = lstCar.iterator();
				while (iter2.hasNext()) {
					car = iter2.next();
					if (car.isManufacturingDone()) {
						iter2.remove();
						onRemove(car, lstCar);
						return car;
					}
				}
			}
			synchronized (testPartialTestedOnDefectsLock) {
				testPartialTestedOnDefectsLock.wait();
			}

		}
	}
	
	@Override
	public Car takeCarForDefectsTester() throws RemoteException,
			InterruptedException {
		Car car = null;
		while (true) {
			car = lstPartialCarTestedOnCompleteness.poll();
			if (car != null){
				onRemove(car, lstPartialCarTestedOnCompleteness);
				return car;
			}
			synchronized (lstCarLock) {
				Iterator<Car> iter2 = lstCar.iterator();
				while (iter2.hasNext()) {
					car = iter2.next();
					if (car.isManufacturingDone()) {
						iter2.remove();
						onRemove(car, lstCar);
						return car;
					}
				}
			}
			synchronized (testPartialTestedOnCompletenessLock) {
				testPartialTestedOnCompletenessLock.wait();
			}

		}
	}
	

	@Override
	public void addForDefectsTester(Car testedCar)
			throws RemoteException, InterruptedException {
		
			lstPartialCarTestedOnDefects.add(testedCar);
			onAdd(testedCar, lstPartialCarTestedOnDefects);
			synchronized(testPartialTestedOnDefectsLock){
				testPartialTestedOnDefectsLock.notifyAll();
			
		}
	}


	@Override
	public void addForCompletenessTester(Car testedCar)
			throws RemoteException, InterruptedException {
		
			lstPartialCarTestedOnCompleteness.add(testedCar);
			onAdd(testedCar, lstPartialCarTestedOnCompleteness);
			synchronized(testPartialTestedOnCompletenessLock){
				testPartialTestedOnCompletenessLock.notifyAll();
		}
		
	}

	@Override
	public void addOrder(Order order) {
		synchronized(lstOrder){
			order.setId(orderId++);
			lstOrder.add(order);
			LinkedBlockingQueue<AssemblyJob> tmp = new LinkedBlockingQueue<AssemblyJob>();
			LinkedBlockingQueue<CoaterJob> tmp2 = new LinkedBlockingQueue<CoaterJob>();
			for (int i=0; i<order.getNumCars(); i++) {
				tmp.add(new AssemblyJob());
				tmp2.add(new CoaterJob());
			}
			assemblerJobs.put(order, tmp);
			coaterJobs.put(order, tmp2);
			orderedCarBodies.put(order, new LinkedBlockingQueue<CarBody>());
			onAdd(order, lstOrder);
		}
		
	}
	
	public AssemblyJob getAssemblyJob(Order order){
		LinkedBlockingQueue<AssemblyJob> a = assemblerJobs.get(order);
		return 		a.poll();
	}
	
	public void putAssemblyJob(AssemblyJob job, Order order){
		assemblerJobs.get(order).add(job);
	}
	
	public CoaterJob getCoaterJob(Order order){
		return coaterJobs.get(order).poll();
	}
	
	public void putCoaterJob(CoaterJob job, Order order){
		coaterJobs.get(order).add(job);
	}

	@Override
	public ArrayList<Order> ReadOrders() throws RemoteException,
			InterruptedException {
		return lstOrder;	  
	}
	
	@Override
	public void removeOrder(Order o) throws RemoteException,
		InterruptedException {
		synchronized (lstOrder) {
			lstOrder.remove(o);
		}
	}

	@Override
	public CarBody takeUncoatedCarBody() {
		Iterator<CarBody> iter2 = lstCarBody.iterator();
		while (iter2.hasNext()) {
			CarBody body = iter2.next();
			if (!body.isCoated()) {
				iter2.remove();
				onRemove(body, lstCarBody);
				return body;
			}
		}
		return null;
	}

    @Override
    public int getNumFinishedCars() throws RemoteException {
        synchronized (lstCarFinished) {
            return lstCarFinished.size();
        }
    }
	
	

}
